package cn.xnatural.app;

import cn.xnatural.app.util.Copier;
import cn.xnatural.app.util.Httper;
import cn.xnatural.app.util.Tailer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.*;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 常用工具集
 */
public class Utils {
    protected static final Logger log = LoggerFactory.getLogger(Utils.class);
    /**
     * 得到jvm进程号
     * @return pid
     */
    public static String pid() { return ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; }


    /**
     * 判断系统是否为 linux 系统
     * 判断方法来源 io.netty.channel.epoll.Native#loadNativeLibrary()
     * @return true: linux
     */
    public static boolean isLinux() {
        return System.getProperty("os.name").toLowerCase(Locale.UK).trim().startsWith("linux");
    }


    /**
     * 项目根目录下边找目录或文件
     * @param child 项目根目录下的子目录/文件
     */
    public static File baseDir(String child) {
        File p = new File(System.getProperty("user.dir"));
        if (child != null) {return new File(p, child);}
        return p;
    }


    /**
     * 本机ipv4地址
     */
    public static String ipv4() {
        try {
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
                NetworkInterface current = en.nextElement();
                if (!current.isUp() || current.isLoopback() || current.isVirtual()) continue;
                Enumeration<InetAddress> addresses = current.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress addr = addresses.nextElement();
                    if (addr.isLoopbackAddress()) continue;
                    if (addr instanceof Inet4Address) {
                        return addr.getHostAddress();
                    }
                }
            }
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
        return null;
    }


    /**
     * sha1 加密
     * @param bs 被加密码byte[]
     * @return 加密后 byte[]
     */
    public static byte[] sha1(byte[] bs) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(bs);
            return digest.digest();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * md5 hex
     * @param bs 被加密的byte[]
     * @return md5加密后的字符串
     */
    public static String md5Hex(byte[] bs) {
        try {
            byte[] secretBytes = MessageDigest.getInstance("md5").digest(bs);
            String md5code = new BigInteger(1, secretBytes).toString(16);
            for (int i = 0; i < 32 - md5code.length(); i++) {
                md5code = "0" + md5code;
            }
            return md5code;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }


    public static final char[] CS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();

    public static String nanoId() { return nanoId(21, CS); }

    public static String nanoId(int len) { return nanoId(len, CS); }

    /**
     * nano id 生成
     * @param len 生成的长度
     * @param CS 字符集
     */
    public static String nanoId(int len, char[] CS) {
        if (len < 1) throw new IllegalArgumentException("Param len must >= 1");
        if (CS == null || CS.length < 1) throw new IllegalArgumentException("Param CS required");
        SecureRandom SR = new SecureRandom();
        final int mask = (2 << (int)Math.floor(Math.log(CS.length - 1) / Math.log(2))) - 1;
        final int step = (int)Math.ceil(1.6 * mask * len / CS.length);
        final StringBuilder sb = new StringBuilder();
        while(true) {
            byte[] bytes = new byte[step];
            SR.nextBytes(bytes);
            for(int i = 0; i < step; ++i) {
                int idx = bytes[i] & mask;
                if (idx < CS.length) {
                    sb.append(CS[idx]);
                    if (sb.length() == len) {
                        return sb.toString();
                    }
                }
            }
        }
    }


    public static String base62(long num) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            sb.insert(0, CS[(int) (num % CS.length)]);
            num /= CS.length;
        }
        return sb.toString();
    }


    /**
     * 基于时间戳的全局ID生成
     */
    public static String msID(int len) {
        StringBuilder sb = new StringBuilder();
        sb.append(base62(System.currentTimeMillis()));
        if (len < sb.length()) throw new IllegalArgumentException("Param len must >= " + sb.length());
        SecureRandom sr = new SecureRandom();
        for (int i = len - sb.length(); i > 0; i--) {
            sb.append(CS[sr.nextInt(CS.length)]);
        }
        return sb.toString();
    }


    /**
     * 类型转换
     * @param v 值
     * @param type 转换的类型
     * @return 转换后的结果
     */
    public static <T> T to(Object v, Class<T> type) {
        if (type == null) return (T) v;
        if (v == null) {
            if (boolean.class.equals(type)) return (T) Boolean.FALSE;
            if (short.class.equals(type)) return (T) Short.valueOf("0");
            if (byte.class.equals(type)) return (T) Byte.valueOf("0");
            if (int.class.equals(type)) return (T) Integer.valueOf(0);
            if (long.class.equals(type)) return (T) Long.valueOf(0);
            if (double.class.equals(type)) return (T) Double.valueOf(0);
            if (float.class.equals(type)) return (T) Float.valueOf(0);
            if (char.class.equals(type)) return (T) Character.valueOf('\u0000');
            return null;
        }
        else if (type.isAssignableFrom(v.getClass())) return (T) v;
        else if (String.class.equals(type)) {
            if (v instanceof InputStream || v instanceof File) {
                StringBuilder sb = new StringBuilder();
                try (Reader rd = new InputStreamReader(v instanceof File ? Files.newInputStream(((File) v).toPath()) : (InputStream) v)) {
                    while (true) {
                        char[] bs = new char[3069];
                        int len = rd.read(bs);
                        if (len == -1) break;
                        sb.append(bs, 0, len);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                return (T) sb.toString();
            }
            if (v instanceof Throwable) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                ((Throwable) v).printStackTrace(pw);
                return (T) sw.toString();
            }
            return (T) v.toString();
        }
        else if (Boolean.class.equals(type) || boolean.class.equals(type)) return (T) Boolean.valueOf(v.toString());
        else if (Short.class.equals(type) || short.class.equals(type)) return (T) Short.valueOf(v.toString());
        else if (Byte.class.equals(type) || byte.class.equals(type)) return (T) Byte.valueOf(v.toString());
        else if (Integer.class.equals(type) || int.class.equals(type)) return (T) Integer.valueOf(v.toString());
        else if (BigInteger.class.equals(type)) return (T) new BigInteger(v.toString());
        else if (Long.class.equals(type) || long.class.equals(type)) return (T) Long.valueOf(v.toString());
        else if (Double.class.equals(type) || double.class.equals(type)) return (T) Double.valueOf(v.toString());
        else if (Float.class.equals(type) || float.class.equals(type)) return (T) Float.valueOf(v.toString());
        else if (BigDecimal.class.equals(type)) return (T) new BigDecimal(v.toString());
        else if (AtomicInteger.class.equals(type)) return (T) new AtomicInteger(Integer.valueOf(v.toString()));
        else if (AtomicLong.class.equals(type)) return (T) new AtomicLong(Long.valueOf(v.toString()));
        else if (LongAdder.class.equals(type)) {
            LongAdder la = new LongAdder();
            la.add(Long.valueOf(v.toString()));
            return (T) la;
        }
        else if (URI.class.equals(type)) return (T) URI.create(v.toString());
        else if (Duration.class.equals(type)) {
            String s = v.toString().toLowerCase();
            if (s.isEmpty()) return (T) Duration.ofMillis(0);
            if (s.endsWith("s")) return (T) Duration.ofSeconds(Long.parseLong(s.replace("s", "")));
            if (s.endsWith("m")) return (T) Duration.ofMinutes(Long.parseLong(s.replace("m", "")));
            if (s.endsWith("h")) return (T) Duration.ofHours(Long.parseLong(s.replace("h", "")));
            if (s.endsWith("d")) return (T) Duration.ofDays(Long.parseLong(s.replace("d", "")));
            return (T) Duration.ofMillis(Long.parseLong(s.replace("ms", ""))); // 默认当毫秒处理
        }
        else if (type.isEnum()) return Arrays.stream(type.getEnumConstants()).filter((o) -> v.equals(((Enum) o).name())).findFirst().orElse(null);
        else if (v instanceof Annotation && type.isAssignableFrom(Map.class)) {
            return (T) new Function<Annotation, Map<String, Object>>() {
                @Override
                public Map<String, Object> apply(Annotation anno) {
                    Map<String, Object> attr = new HashMap<>();
                    Method[] ms = anno.annotationType().getDeclaredMethods();
                    for (Method m : ms) {
                        try {
                            Object v = m.invoke(anno);
                            if (v.getClass().isArray() && v.getClass().getComponentType().isAnnotation()) {
                                List<Map<String, Object>> ls = new LinkedList<>(); attr.put(m.getName(), ls);
                                for (Annotation a : ((Annotation[]) v)) {
                                    ls.add(apply(a));
                                }
                            } else {
                                attr.put(m.getName(), v instanceof Annotation ? apply((Annotation) v) : v);
                            }
                        } catch (Exception e) {
                            log.warn("", e);
                        }
                    }
                    return attr;
                }
            }.apply((Annotation) v);
        }
        return (T) v;
    }


    /**
     * 遍历所有方法并处理
     * @param clz Class
     * @param fn 函数
     */
    public static void iterateMethod(final Class<?> clz, Consumer<Method> fn) {
        if (fn == null) return;
        Class<?> c = clz;
        do {
            for (Method m : c.getDeclaredMethods()) fn.accept(m);
            c = c.getSuperclass();
        } while (c != null);
    }


    /**
     * 查找字段
     * @param clz Class
     * @param fn 函数
     */
    public static void iterateField(final Class<?> clz, Consumer<Field> fn) {
        if (fn == null) return;
        Class<?> c = clz;
        do {
            for (Field f : c.getDeclaredFields()) fn.accept(f);
            c = c.getSuperclass();
        } while (c != null);
    }


    /**
     * 扫描类处理
     * @param pkgName 包名
     * @param fn 类处理函数
     */
    public static void scan(String pkgName, Consumer<Class<?>> fn) {
        scan(pkgName, Thread.currentThread().getContextClassLoader(), clz -> { fn.accept(clz); return true; });
    }


    /**
     * 扫描类处理
     * @param pkgName 包名
     * @param loader 类加载器
     * @param fn 类处理函数
     */
    public static void scan(String pkgName, ClassLoader loader, Function<Class<?>, Boolean> fn) {
        if (pkgName == null || pkgName.isEmpty()) return;
        if (loader == null) return;
        try {
            Function<File, Boolean> handle = new Function<File, Boolean>() {
                @Override
                public Boolean apply(File f) {
                    if (f.isDirectory()) {
                        File[] fs = f.listFiles();
                        if (fs != null) {
                            for (File ff : fs) {
                                Boolean isContinue = apply(ff);
                                if (!isContinue) return false;
                            }
                        }
                    } else {
                        if (f.getName().endsWith(".class")) {
                            try {
                                String path = f.getAbsolutePath().replaceAll("\\\\", "/");
                                String className = path.substring(path.lastIndexOf(pkgName.replaceAll("\\.", "/"))).replace(".class", "").replace("/", ".");
                                if (fn != null && !fn.apply(Class.forName(className))) return false;
                            } catch (ClassNotFoundException ignore) {}
                        }
                    }
                    return true;
                }
            };
            for (Enumeration<URL> e = loader.getResources(pkgName.replaceAll("\\.", "/")); e.hasMoreElements(); ) {
                URL url = e.nextElement();
                if ("file".equals(url.getProtocol())) {
                    File f = new File(url.getFile());
                    try {
                        if (!handle.apply(f)) return;
                    } catch (Throwable ignore) {}
                } else if ("jar".equals(url.getProtocol())) {
                    for (Enumeration<JarEntry> it = ((JarURLConnection) url.openConnection()).getJarFile().entries(); it.hasMoreElements(); ) {
                        JarEntry je = it.nextElement();
                        if (!je.getName().endsWith(".class")) continue;
                        try {
                            String className = je.getName().replace(".class", "").replace("/", ".");
                            if (fn != null && !fn.apply(Class.forName(className))) return;
                        } catch (Throwable ignore) {}
                    }
                }
            }
        } catch (Exception e) {
            log.error("scan error", e);
        }
    }


    /**
     * 构建一个 http 请求, 支持 get, post. 文件上传.
     */
    public static Httper http(String url) { return new Httper(url); }


    /**
     * 文件内容监控器(类linux tail)
     * @return {@link Tailer}
     */
    public static Tailer tailer() { return new Tailer(); }


    /**
     * 对象 复制器
     * @param src 源对象
     * @param target 目标对象
     * @param <S> 源对象类型
     * @param <T> 目标对象类型
     * @return {@link Copier}
     */
    public static <S, T> Copier<S, T> copier(S src, T target) { return new Copier<S, T>(src, target); }


    /**
     * io 流 copy
     * <code>
     *     try (InputStream is = new FileInputStream("d:/tmp/1.txt"); OutputStream os = new FileOutputStream("d:/tmp/2.txt")) {
     *         Utils.ioCopy(is, os);
     *     }
     * </code>
     * @param is 输入流
     * @param os 输出流
     * @param bufSize 每次读取字节大小
     * @return 总复制大小
     * @throws IOException {@link OutputStream#write(byte[], int, int)}
     */
    public static long ioCopy(InputStream is, OutputStream os, Integer bufSize) throws IOException {
        byte[] buf = new byte[bufSize == null || bufSize < 1 ? 5120 : bufSize];
        long count = 0;
        int len;
        while (-1 != (len = is.read(buf))) {
            os.write(buf, 0, len);
            count += len;
        }
        return count;
    }

    /**
     * io 流 copy
     * @param is 输入流
     * @param os 输出流
     * @return 总复制大小
     * @throws IOException {@link OutputStream#write(byte[], int, int)}
     */
    public static long ioCopy(InputStream is, OutputStream os) throws IOException {return ioCopy(is, os, 4096);}


    public final static Pattern PLACEHOOLDE_RPATTERN = Pattern.compile("\\$\\{(.+?)\\}");
    /**
     * ${} 占位符替换
     */
    public static String placeholder(String content, Map<String, String> ctx) {
        if (content == null) return null;
        if (!content.contains("${")) return content;
        Matcher m = PLACEHOOLDE_RPATTERN.matcher(content);
        StringBuffer sb = new StringBuffer();
        while (m.find()) {
            // 获取匹配的占位符名称，比如 "name"
            String key = m.group(1);
            // 使用Map中的值替换占位符，如果找不到则保持原样
            m.appendReplacement(sb, ctx.getOrDefault(key, m.group()));
        }
        m.appendTail(sb);
        return sb.toString();
    }


    /**
     * 字符串转驼峰
     */
    public static String hump(String input) {
        StringBuilder sb = new StringBuilder();
        boolean nextUpperCase = false;

        for (char c : input.toCharArray()) {
            if (c == '_') {
                nextUpperCase = true;
            } else {
                if (nextUpperCase) {
                    sb.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    sb.append(c);
                }
            }
        }
        return sb.toString();
    }


    /**
     * 通用本地命令执行
     * @param cmd 完整命令
     */
    public static String cmd(String cmd, Duration timeout) throws IOException, InterruptedException {
        timeout = timeout == null ? Duration.ofSeconds(30) : timeout;
        long ms = timeout.toMillis();
        log.info("Executing command: {}, {}", cmd, ms);
        Process p = null;
        File output = null;
        File error = null;
        try {
            String filename = md5Hex(cmd.getBytes());
            output = new File("/dev/shm/" + filename + ".output");
            if (output.exists()) output.delete();
            output.getParentFile().mkdirs(); output.createNewFile();
            error = new File("/dev/shm/" + filename + ".error");
            if (error.exists()) error.delete();
            error.createNewFile();
            ProcessBuilder pb = new ProcessBuilder(cmd.split("\\ "));
            p = pb.redirectOutput(output).redirectError(error).start();
            if (!p.waitFor(ms, TimeUnit.MILLISECONDS)) { // 超时，强制结束进程
                p.destroyForcibly();
                throw new InterruptedException("Subprocess did not terminate within the timeout. " + cmd);
            }
            return p.exitValue() == 0 ? new String(Files.readAllBytes(output.toPath())) : new String(Files.readAllBytes(error.toPath()));
        } finally {
            if (p != null) p.destroyForcibly();
            if (output != null && output.exists()) output.delete();
            if (error != null && error.exists()) error.delete();
        }
    }
}
